// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ui/chromeos/ime/candidate_window_view.h"

#include <stddef.h>

#include <string>

#include "base/macros.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/chromeos/ime/candidate_view.h"
#include "ui/views/test/views_test_base.h"
#include "ui/views/widget/widget.h"

namespace ui {
namespace ime {

    namespace {
        const char* kSampleCandidate[] = {
            "Sample Candidate 1",
            "Sample Candidate 2",
            "Sample Candidate 3"
        };
        const char* kSampleAnnotation[] = {
            "Sample Annotation 1",
            "Sample Annotation 2",
            "Sample Annotation 3"
        };
        const char* kSampleDescriptionTitle[] = {
            "Sample Description Title 1",
            "Sample Description Title 2",
            "Sample Description Title 3",
        };
        const char* kSampleDescriptionBody[] = {
            "Sample Description Body 1",
            "Sample Description Body 2",
            "Sample Description Body 3",
        };

        void InitCandidateWindow(size_t page_size,
            ui::CandidateWindow* candidate_window)
        {
            candidate_window->set_cursor_position(0);
            candidate_window->set_page_size(page_size);
            candidate_window->mutable_candidates()->clear();
            candidate_window->set_orientation(ui::CandidateWindow::VERTICAL);
        }

        void InitCandidateWindowWithCandidatesFilled(
            size_t page_size,
            ui::CandidateWindow* candidate_window)
        {
            InitCandidateWindow(page_size, candidate_window);
            for (size_t i = 0; i < page_size; ++i) {
                ui::CandidateWindow::Entry entry;
                entry.value = base::UTF8ToUTF16(base::StringPrintf(
                    "value %lld", static_cast<unsigned long long>(i)));
                entry.label = base::UTF8ToUTF16(base::StringPrintf(
                    "%lld", static_cast<unsigned long long>(i)));
                candidate_window->mutable_candidates()->push_back(entry);
            }
        }

    } // namespace

    class CandidateWindowViewTest : public views::ViewsTestBase {
    public:
        CandidateWindowViewTest() { }
        ~CandidateWindowViewTest() override { }

    protected:
        void SetUp() override
        {
            views::ViewsTestBase::SetUp();
            candidate_window_view_ = new CandidateWindowView(GetContext());
            candidate_window_view_->InitWidget();
        }

        void TearDown() override
        {
            candidate_window_view_->GetWidget()->CloseNow();
            views::ViewsTestBase::TearDown();
        }

        CandidateWindowView* candidate_window_view()
        {
            return candidate_window_view_;
        }

        int selected_candidate_index_in_page()
        {
            return candidate_window_view_->selected_candidate_index_in_page_;
        }

        size_t GetCandidatesSize() const
        {
            return candidate_window_view_->candidate_views_.size();
        }

        CandidateView* GetCandidateAt(size_t i)
        {
            return candidate_window_view_->candidate_views_[i];
        }

        void SelectCandidateAt(int index_in_page)
        {
            candidate_window_view_->SelectCandidateAt(index_in_page);
        }

        void MaybeInitializeCandidateViews(
            const ui::CandidateWindow& candidate_window)
        {
            candidate_window_view_->MaybeInitializeCandidateViews(candidate_window);
        }

        void ExpectLabels(const std::string& shortcut,
            const std::string& candidate,
            const std::string& annotation,
            const CandidateView* row)
        {
            EXPECT_EQ(shortcut, base::UTF16ToUTF8(row->shortcut_label_->text()));
            EXPECT_EQ(candidate, base::UTF16ToUTF8(row->candidate_label_->text()));
            EXPECT_EQ(annotation, base::UTF16ToUTF8(row->annotation_label_->text()));
        }

    private:
        CandidateWindowView* candidate_window_view_; // Owned by its Widget.

        DISALLOW_COPY_AND_ASSIGN(CandidateWindowViewTest);
    };

    TEST_F(CandidateWindowViewTest, UpdateCandidatesTest_CursorVisibility)
    {
        // Visible (by default) cursor.
        ui::CandidateWindow candidate_window;
        const int candidate_window_size = 9;
        InitCandidateWindowWithCandidatesFilled(candidate_window_size,
            &candidate_window);
        candidate_window_view()->UpdateCandidates(candidate_window);
        EXPECT_EQ(0, selected_candidate_index_in_page());

        // Invisible cursor.
        candidate_window.set_is_cursor_visible(false);
        candidate_window_view()->UpdateCandidates(candidate_window);
        EXPECT_EQ(-1, selected_candidate_index_in_page());

        // Move the cursor to the end.
        candidate_window.set_cursor_position(candidate_window_size - 1);
        candidate_window_view()->UpdateCandidates(candidate_window);
        EXPECT_EQ(-1, selected_candidate_index_in_page());

        // Change the cursor to visible.  The cursor must be at the end.
        candidate_window.set_is_cursor_visible(true);
        candidate_window_view()->UpdateCandidates(candidate_window);
        EXPECT_EQ(candidate_window_size - 1, selected_candidate_index_in_page());
    }

    TEST_F(CandidateWindowViewTest, SelectCandidateAtTest)
    {
        // Set 9 candidates.
        ui::CandidateWindow candidate_window_large;
        const int candidate_window_large_size = 9;
        InitCandidateWindowWithCandidatesFilled(candidate_window_large_size,
            &candidate_window_large);
        candidate_window_large.set_cursor_position(candidate_window_large_size - 1);
        candidate_window_view()->UpdateCandidates(candidate_window_large);

        // Select the last candidate.
        SelectCandidateAt(candidate_window_large_size - 1);

        // Reduce the number of candidates to 3.
        ui::CandidateWindow candidate_window_small;
        const int candidate_window_small_size = 3;
        InitCandidateWindowWithCandidatesFilled(candidate_window_small_size,
            &candidate_window_small);
        candidate_window_small.set_cursor_position(candidate_window_small_size - 1);
        // Make sure the test doesn't crash if the candidate window reduced
        // its size. (crbug.com/174163)
        candidate_window_view()->UpdateCandidates(candidate_window_small);
        SelectCandidateAt(candidate_window_small_size - 1);
    }

    TEST_F(CandidateWindowViewTest, ShortcutSettingTest)
    {
        const char* kEmptyLabel = "";
        const char* kCustomizedLabel[] = { "a", "s", "d" };
        const char* kExpectedHorizontalCustomizedLabel[] = { "a.", "s.", "d." };

        {
            SCOPED_TRACE("candidate_views allocation test");
            const size_t kMaxPageSize = 16;
            for (size_t i = 1; i < kMaxPageSize; ++i) {
                ui::CandidateWindow candidate_window;
                InitCandidateWindow(i, &candidate_window);
                candidate_window_view()->UpdateCandidates(candidate_window);
                EXPECT_EQ(i, GetCandidatesSize());
            }
        }
        {
            SCOPED_TRACE("Empty string for each labels expects empty labels(vertical)");
            const size_t kPageSize = 3;
            ui::CandidateWindow candidate_window;
            InitCandidateWindow(kPageSize, &candidate_window);

            candidate_window.set_orientation(ui::CandidateWindow::VERTICAL);
            for (size_t i = 0; i < kPageSize; ++i) {
                ui::CandidateWindow::Entry entry;
                entry.value = base::UTF8ToUTF16(kSampleCandidate[i]);
                entry.annotation = base::UTF8ToUTF16(kSampleAnnotation[i]);
                entry.description_title = base::UTF8ToUTF16(kSampleDescriptionTitle[i]);
                entry.description_body = base::UTF8ToUTF16(kSampleDescriptionBody[i]);
                entry.label = base::UTF8ToUTF16(kEmptyLabel);
                candidate_window.mutable_candidates()->push_back(entry);
            }

            candidate_window_view()->UpdateCandidates(candidate_window);

            ASSERT_EQ(kPageSize, GetCandidatesSize());
            for (size_t i = 0; i < kPageSize; ++i) {
                ExpectLabels(kEmptyLabel, kSampleCandidate[i], kSampleAnnotation[i],
                    GetCandidateAt(i));
            }
        }
        {
            SCOPED_TRACE(
                "Empty string for each labels expect empty labels(horizontal)");
            const size_t kPageSize = 3;
            ui::CandidateWindow candidate_window;
            InitCandidateWindow(kPageSize, &candidate_window);

            candidate_window.set_orientation(ui::CandidateWindow::HORIZONTAL);
            for (size_t i = 0; i < kPageSize; ++i) {
                ui::CandidateWindow::Entry entry;
                entry.value = base::UTF8ToUTF16(kSampleCandidate[i]);
                entry.annotation = base::UTF8ToUTF16(kSampleAnnotation[i]);
                entry.description_title = base::UTF8ToUTF16(kSampleDescriptionTitle[i]);
                entry.description_body = base::UTF8ToUTF16(kSampleDescriptionBody[i]);
                entry.label = base::UTF8ToUTF16(kEmptyLabel);
                candidate_window.mutable_candidates()->push_back(entry);
            }

            candidate_window_view()->UpdateCandidates(candidate_window);

            ASSERT_EQ(kPageSize, GetCandidatesSize());
            // Confirm actual labels not containing ".".
            for (size_t i = 0; i < kPageSize; ++i) {
                ExpectLabels(kEmptyLabel, kSampleCandidate[i], kSampleAnnotation[i],
                    GetCandidateAt(i));
            }
        }
        {
            SCOPED_TRACE("Vertical customized label case");
            const size_t kPageSize = 3;
            ui::CandidateWindow candidate_window;
            InitCandidateWindow(kPageSize, &candidate_window);

            candidate_window.set_orientation(ui::CandidateWindow::VERTICAL);
            for (size_t i = 0; i < kPageSize; ++i) {
                ui::CandidateWindow::Entry entry;
                entry.value = base::UTF8ToUTF16(kSampleCandidate[i]);
                entry.annotation = base::UTF8ToUTF16(kSampleAnnotation[i]);
                entry.description_title = base::UTF8ToUTF16(kSampleDescriptionTitle[i]);
                entry.description_body = base::UTF8ToUTF16(kSampleDescriptionBody[i]);
                entry.label = base::UTF8ToUTF16(kCustomizedLabel[i]);
                candidate_window.mutable_candidates()->push_back(entry);
            }

            candidate_window_view()->UpdateCandidates(candidate_window);

            ASSERT_EQ(kPageSize, GetCandidatesSize());
            // Confirm actual labels not containing ".".
            for (size_t i = 0; i < kPageSize; ++i) {
                ExpectLabels(kCustomizedLabel[i],
                    kSampleCandidate[i],
                    kSampleAnnotation[i],
                    GetCandidateAt(i));
            }
        }
        {
            SCOPED_TRACE("Horizontal customized label case");
            const size_t kPageSize = 3;
            ui::CandidateWindow candidate_window;
            InitCandidateWindow(kPageSize, &candidate_window);

            candidate_window.set_orientation(ui::CandidateWindow::HORIZONTAL);
            for (size_t i = 0; i < kPageSize; ++i) {
                ui::CandidateWindow::Entry entry;
                entry.value = base::UTF8ToUTF16(kSampleCandidate[i]);
                entry.annotation = base::UTF8ToUTF16(kSampleAnnotation[i]);
                entry.description_title = base::UTF8ToUTF16(kSampleDescriptionTitle[i]);
                entry.description_body = base::UTF8ToUTF16(kSampleDescriptionBody[i]);
                entry.label = base::UTF8ToUTF16(kCustomizedLabel[i]);
                candidate_window.mutable_candidates()->push_back(entry);
            }

            candidate_window_view()->UpdateCandidates(candidate_window);

            ASSERT_EQ(kPageSize, GetCandidatesSize());
            // Confirm actual labels not containing ".".
            for (size_t i = 0; i < kPageSize; ++i) {
                ExpectLabels(kExpectedHorizontalCustomizedLabel[i],
                    kSampleCandidate[i],
                    kSampleAnnotation[i],
                    GetCandidateAt(i));
            }
        }
    }

    TEST_F(CandidateWindowViewTest, DoNotChangeRowHeightWithLabelSwitchTest)
    {
        const size_t kPageSize = 10;
        ui::CandidateWindow candidate_window;
        ui::CandidateWindow no_shortcut_candidate_window;

        const base::string16 kSampleCandidate1 = base::UTF8ToUTF16(
            "Sample String 1");
        const base::string16 kSampleCandidate2 = base::UTF8ToUTF16(
            "\xE3\x81\x82"); // multi byte string.
        const base::string16 kSampleCandidate3 = base::UTF8ToUTF16(".....");

        const base::string16 kSampleShortcut1 = base::UTF8ToUTF16("1");
        const base::string16 kSampleShortcut2 = base::UTF8ToUTF16("b");
        const base::string16 kSampleShortcut3 = base::UTF8ToUTF16("C");

        const base::string16 kSampleAnnotation1 = base::UTF8ToUTF16(
            "Sample Annotation 1");
        const base::string16 kSampleAnnotation2 = base::UTF8ToUTF16(
            "\xE3\x81\x82"); // multi byte string.
        const base::string16 kSampleAnnotation3 = base::UTF8ToUTF16("......");

        // Create CandidateWindow object.
        InitCandidateWindow(kPageSize, &candidate_window);

        candidate_window.set_cursor_position(0);
        candidate_window.set_page_size(3);
        candidate_window.mutable_candidates()->clear();
        candidate_window.set_orientation(ui::CandidateWindow::VERTICAL);
        no_shortcut_candidate_window.CopyFrom(candidate_window);

        ui::CandidateWindow::Entry entry;
        entry.value = kSampleCandidate1;
        entry.annotation = kSampleAnnotation1;
        candidate_window.mutable_candidates()->push_back(entry);
        entry.label = kSampleShortcut1;
        no_shortcut_candidate_window.mutable_candidates()->push_back(entry);

        entry.value = kSampleCandidate2;
        entry.annotation = kSampleAnnotation2;
        candidate_window.mutable_candidates()->push_back(entry);
        entry.label = kSampleShortcut2;
        no_shortcut_candidate_window.mutable_candidates()->push_back(entry);

        entry.value = kSampleCandidate3;
        entry.annotation = kSampleAnnotation3;
        candidate_window.mutable_candidates()->push_back(entry);
        entry.label = kSampleShortcut3;
        no_shortcut_candidate_window.mutable_candidates()->push_back(entry);

        int before_height = 0;

        // Test for shortcut mode to no-shortcut mode.
        // Initialize with a shortcut mode candidate window.
        MaybeInitializeCandidateViews(candidate_window);
        ASSERT_EQ(3UL, GetCandidatesSize());
        // Check the selected index is invalidated.
        EXPECT_EQ(-1, selected_candidate_index_in_page());
        before_height = GetCandidateAt(0)->GetContentsBounds().height();
        // Checks all entry have same row height.
        for (size_t i = 1; i < GetCandidatesSize(); ++i)
            EXPECT_EQ(before_height, GetCandidateAt(i)->GetContentsBounds().height());

        // Initialize with a no shortcut mode candidate window.
        MaybeInitializeCandidateViews(no_shortcut_candidate_window);
        ASSERT_EQ(3UL, GetCandidatesSize());
        // Check the selected index is invalidated.
        EXPECT_EQ(-1, selected_candidate_index_in_page());
        EXPECT_EQ(before_height, GetCandidateAt(0)->GetContentsBounds().height());
        // Checks all entry have same row height.
        for (size_t i = 1; i < GetCandidatesSize(); ++i)
            EXPECT_EQ(before_height, GetCandidateAt(i)->GetContentsBounds().height());

        // Test for no-shortcut mode to shortcut mode.
        // Initialize with a no shortcut mode candidate window.
        MaybeInitializeCandidateViews(no_shortcut_candidate_window);
        ASSERT_EQ(3UL, GetCandidatesSize());
        // Check the selected index is invalidated.
        EXPECT_EQ(-1, selected_candidate_index_in_page());
        before_height = GetCandidateAt(0)->GetContentsBounds().height();
        // Checks all entry have same row height.
        for (size_t i = 1; i < GetCandidatesSize(); ++i)
            EXPECT_EQ(before_height, GetCandidateAt(i)->GetContentsBounds().height());

        // Initialize with a shortcut mode candidate window.
        MaybeInitializeCandidateViews(candidate_window);
        ASSERT_EQ(3UL, GetCandidatesSize());
        // Check the selected index is invalidated.
        EXPECT_EQ(-1, selected_candidate_index_in_page());
        EXPECT_EQ(before_height, GetCandidateAt(0)->GetContentsBounds().height());
        // Checks all entry have same row height.
        for (size_t i = 1; i < GetCandidatesSize(); ++i)
            EXPECT_EQ(before_height, GetCandidateAt(i)->GetContentsBounds().height());
    }

} // namespace ime
} // namespace ui
